package com.skyon.rule.res;
/**
 * 提供加载外部配置文件功能，构建KnowledgeBase后，提供外部调用stateless session 和stateful session
 * 
 * 
 * @author cfans
 */
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;

import org.apache.commons.lang.StringUtils;
import org.drools.KnowledgeBase;
import org.drools.KnowledgeBaseConfiguration;
import org.drools.KnowledgeBaseFactoryService;
import org.drools.agent.KnowledgeAgent;
import org.drools.base.accumulators.AccumulateFunction;
import org.drools.base.evaluators.EvaluatorDefinition;
import org.drools.builder.KnowledgeBuilder;
import org.drools.builder.KnowledgeBuilderErrors;
import org.drools.builder.KnowledgeBuilderFactory;
import org.drools.builder.KnowledgeBuilderFactoryService;
import org.drools.builder.ResourceType;
import org.drools.builder.conf.AccumulateFunctionOption;
import org.drools.builder.conf.EvaluatorOption;
import org.drools.compiler.PackageBuilderConfiguration;

import org.drools.io.impl.ByteArrayResource;
import org.drools.runtime.KnowledgeSessionConfiguration;
import org.drools.runtime.StatefulKnowledgeSession;
import org.drools.runtime.StatelessKnowledgeSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.skyon.rule.res.exceptions.PlotBuilderException;
import com.skyon.rule.res.exceptions.InvalidMappingException;
import com.skyon.rule.res.exceptions.InvalidRuleSourceException;
import com.skyon.rule.res.exceptions.MappingException;
import com.skyon.rule.res.exceptions.MappingNotFoundException;
import com.skyon.rule.res.spring.grid.GridNode;
import com.skyon.rule.res.spring.grid.GridNodeImpl;
import com.skyon.rule.res.utils.IOUtil;


public class PlotConfiguration implements Serializable {
	private static final long serialVersionUID = 7269180843743247926L;
	private static Logger log = LoggerFactory.getLogger( PlotConfiguration.class );
	
	private static final String              POINT = ".";
	private static final String              RESOURCES_FOLDER = "RESOURCES/";

	
	
	
	protected final SettingsFactory          settingsFactory;
    private GridNode                         node;
    private String                           root = "com.skyon.res.root";
    private List<String>                     flowURLs = new ArrayList<String>();             
	private Map<String,Class<?>>             runtimeClasses = new HashMap<String,Class<?>>();
    private List<String>                     runtimeClassNames = new ArrayList<String>();
	
    private KnowledgeBase                    kbase;
    private KnowledgeBaseConfiguration       baseConf;
    private Map<String, AccumulateFunction>  accumulateFunctions;
    private Map<String, EvaluatorDefinition> evaluators;
    private List<ResourceAdapter>            resources = new ArrayList<ResourceAdapter>(); 
    
    private StatelessKnowledgeSession        statelessKssession;
    private StatefulKnowledgeSession         statefulKssession;
    KnowledgeSessionConfiguration            sessionConf;
    KnowledgeAgent                           kagent;
    
    
	protected PlotConfiguration(SettingsFactory settingsFactory) {
		this.settingsFactory = settingsFactory;
		this.node = new GridNodeImpl();
		reset();
	}

	public PlotConfiguration() {
		this( new SettingsFactory() );
	}

	protected void reset() {
		//TODO 初始化配置文件
		
		
	}
	
	public PlotConfiguration addJar(File jar) throws MappingException {
		log.info( "Searching for rule documents in jar: " + jar.getName() );
		JarFile jarFile = null;
		try {
			try {
				jarFile = new JarFile( jar );
				String jarName = jar.getName();
				this.root = StringUtils.substringBefore(jarName, ".jar");
				this.flowURLs.add(root);
			}
			catch (IOException ioe) {
				ioe.printStackTrace();
				throw new InvalidRuleSourceException(
						"Could not read rule documents from jar: " + jar.getName(),"jar",jar.getName(),ioe
				);
			}
			
			Enumeration<JarEntry> jarEntries = jarFile.entries();
			while ( jarEntries.hasMoreElements() ) {
				ZipEntry ze = (ZipEntry) jarEntries.nextElement();
				InputStream inputStream = null;
				try {
					inputStream = jarFile.getInputStream( ze );
					log.info( "Found source document in jar: " + ze.getName() );
					String entityName = ze.getName();
					assortByType(entityName, inputStream );
					
				}catch( Exception e ) {
					e.printStackTrace();
					throw new InvalidMappingException(
							"Could not read mapping documents from jar: " + jar.getName(), "jar", jar.getName(),e
					);
				}
			}
			
		}
		finally {
			try {
				if ( jarFile != null ) {
					jarFile.close();
				}
			}
			catch (IOException ioe) {
				log.error("could not close jar", ioe);
			}
		}
		
		log.info("root =" + root);
		for (Iterator<String> iterator = flowURLs.iterator(); iterator.hasNext();) {
			String type = (String) iterator.next();
			log.info("flowURLs.one =" + type);
		}
		
		return this;
		
	}

	public PlotConfiguration addDRLFile(final File ruleFile) throws MappingNotFoundException, InvalidRuleSourceException {
		log.info( "Reading RULES from file: " + ruleFile.getPath() );
		final String fullName =  ruleFile.getAbsolutePath();
		final String name = ruleFile.getName();
		
		InputStream inputStream = null;
		try {
			inputStream = new FileInputStream( ruleFile );
		}
		catch ( FileNotFoundException e ) {
			e.printStackTrace();
			throw new MappingNotFoundException( "file",fullName);
		}
		assortByType(name,inputStream);
		
		return this;
	}
	
	private void assortByType(String entityName, InputStream inputStream) throws InvalidRuleSourceException {
		
		if ( entityName.toUpperCase().endsWith( ".CLASS" ) ) {
//TODO 为了防止因为jar包版本更新对老的规则xom影响，必须动态加载自己jar包中的calss,会不会有加载顺序影响依赖的情况？
//			int i = 0;
//			PlotBeanClassLoader[] classLoaders = new PlotBeanClassLoader[classes.size()];
//			for (String clsName : this.classes.keySet()) {
//				classLoaders[i].loadClass( clsName,classes.get(clsName) );
//				i++;
//			}
			//生成calss
			try {
//				IOUtil.getBytes(inputStream);

			} catch (Exception e) {
				e.printStackTrace();
				throw new InvalidRuleSourceException(
						"Could not read bytes from inputStream " ,"jar","",e
						);
			}
			String clsName = StringUtils.replace(StringUtils.substringBefore(entityName, ".class"),"/",".");
			log.info("clsName = " + clsName);
			this.runtimeClasses.put( clsName, putCalssesbyName(clsName) );
			this.runtimeClassNames.add(clsName);
		}
		
		if ( entityName.toUpperCase().endsWith( ".DRL" ) ) {
			addInputStream( ResourceType.DRL, inputStream );
		}
		
		if ( entityName.toUpperCase().endsWith( ".RF") ) {
			this.flowURLs.add(root + nameToID(entityName.toUpperCase()));
			addInputStream( ResourceType.DRF, inputStream );
		}
		
		if ( entityName.toUpperCase().endsWith( ".BPMN" ) || entityName.toUpperCase().endsWith( ".BPMN2" ) ) {
			this.flowURLs.add(root + nameToID(entityName.toUpperCase()));
			addInputStream( ResourceType.BPMN2, inputStream );
		}
		
		if ( entityName.toUpperCase().endsWith( ".XLS" ) ) {
			addInputStream( ResourceType.DTABLE, inputStream );
		}
	}

	private Class<?> putCalssesbyName(String clsName) {
		try {
			return Class.forName(clsName, true, this.getClass().getClassLoader());
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}

	private String nameToID(String entityUpCaseName) {
		String urlTmp = StringUtils.replace(entityUpCaseName, RESOURCES_FOLDER, POINT);
		log.info("urlTmp=" + urlTmp);
		String[] ssp = StringUtils.split(urlTmp, POINT);
		String entityID =  POINT + ssp[0];
		return entityID;
	}
	
	protected PlotConfiguration addInputStream(ResourceType resourceType,InputStream inputStream) throws InvalidRuleSourceException {
		ResourceAdapter resAdapter = new ResourceAdapter();
		try {
			resAdapter.setResource(new ByteArrayResource(IOUtil.getBytes(inputStream)));
		} catch (Exception e) {
			e.printStackTrace();
			throw new InvalidRuleSourceException(
					"Could not read bytes from inputStream " ,"jar","",e
					);
		}
		resAdapter.setResourceType(resourceType);
		this.resources.add(resAdapter);
		return this;
	}
	
	
	
	public KnowledgeBase builderkBase() throws PlotBuilderException{

		PackageBuilderConfiguration kconf = (PackageBuilderConfiguration)KnowledgeBuilderFactory
        		.newKnowledgeBuilderConfiguration();

        if ( this.accumulateFunctions != null && !this.accumulateFunctions.isEmpty() ) {
            for ( Entry<String, AccumulateFunction> entry : this.accumulateFunctions.entrySet() ) {
                kconf.setOption( AccumulateFunctionOption.get( entry.getKey(),
                                                               entry.getValue() ) );
            }
        }
        if ( this.evaluators != null && !this.evaluators.isEmpty() ) {
            for ( Entry<String, EvaluatorDefinition> entry : this.evaluators.entrySet() ) {
                kconf.setOption( EvaluatorOption.get( entry.getKey(),
                                                      entry.getValue() ) );
            }
        }

        KnowledgeBuilder kbuilder = node.get(KnowledgeBuilderFactoryService.class).newKnowledgeBuilder(kconf);
        if ( this.baseConf != null ) {
            kbase = node.get( KnowledgeBaseFactoryService.class ).newKnowledgeBase( baseConf );
        } else {
            kbase = node.get( KnowledgeBaseFactoryService.class ).newKnowledgeBase();
        }

        for ( ResourceAdapter res : resources ) {
            if ( res.getResourceConfiguration() == null ) {
                kbuilder.add( res.getDroolsResource(),
                              res.getResourceType() );
            } else {
                kbuilder.add( res.getDroolsResource(),
                              res.getResourceType(),
                              res.getResourceConfiguration() );
            }
        }

        KnowledgeBuilderErrors errors = kbuilder.getErrors();
        if ( !errors.isEmpty() ) {
            throw new PlotBuilderException( errors.toString(),new Exception() );
        }
        
        kbase.addKnowledgePackages( kbuilder.getKnowledgePackages() );
		return kbase;
	}

	public StatelessKnowledgeSession newStatelessSession(){

        if ( this.kagent != null ) {
            statelessKssession = this.kagent.newStatelessKnowledgeSession( getSessionConf() );
        } else {
            statelessKssession = getKbase().newStatelessKnowledgeSession( getSessionConf() );
        }

        if ( getNode() != null ) {
            getNode().set( this.root, this.statelessKssession );
        }
		return statelessKssession;
	}

	
	public StatefulKnowledgeSession newStatefulSession(){
		statefulKssession = getKbase().newStatefulKnowledgeSession( getSessionConf() ,null );
        if ( getNode() != null ) {
            getNode().set( this.root, this.statefulKssession );
        }
		return statefulKssession;
	}

	
	public KnowledgeAgent getKagent() {
		return kagent;
	}

	public void setKagent(KnowledgeAgent kagent) {
		this.kagent = kagent;
	}

	public GridNode getNode() {
		return node;
	}

	public void setNode(GridNode node) {
		this.node = node;
	}

	public KnowledgeBase getKbase() {
		return kbase;
	}

	public void setKbase(KnowledgeBase kbase) {
		this.kbase = kbase;
	}


	public List<ResourceAdapter> getResources() {
		return resources;
	}

	public void setResources(List<ResourceAdapter> resources) {
		this.resources = resources;
	}

	public KnowledgeBaseConfiguration getBaseConf() {
		return baseConf;
	}

	public void setBaseConf(KnowledgeBaseConfiguration baseConf) {
		this.baseConf = baseConf;
	}

	public KnowledgeSessionConfiguration getSessionConf() {
		return sessionConf;
	}

	public void setSessionConf(KnowledgeSessionConfiguration sessionConf) {
		this.sessionConf = sessionConf;
	}

	public List<String> getFlowURLs() {
		return flowURLs;
	}
	
	public String getRoot() {
		return root;
	}

	public Map<String, Class<?>> getRuntimeClasses() {
		return runtimeClasses;
	}

	public void setRuntimeClasses(Map<String, Class<?>> runtimeClasses) {
		this.runtimeClasses = runtimeClasses;
	}

	public List<String> getRuntimeClassNames() {
		return runtimeClassNames;
	}

}
